#! encoding:utf-8

import socket
import ssl
import logging
"""
2017/02/16
作业 1


资料:
在 Python3 中，bytes 和 str 的互相转换方式是
str.encode('utf-8')
bytes.decode('utf-8')

send 函数的参数和 recv 函数的返回值都是 bytes 类型
其他请参考上课内容, 不懂在群里发问, 不要憋着
"""


# log
def __log():
    # create logger with 'spam_application'
    logger = logging.getLogger('client')
    logger.setLevel(logging.DEBUG)
    # create file handler which logs even debug messages
    fh = logging.FileHandler('./log/client.log')
    fh.setLevel(logging.DEBUG)
    # create console handler with a higher log level
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    # create formatter and add it to the handlers
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    fh.setFormatter(formatter)
    ch.setFormatter(formatter)
    # add the handlers to the logger
    logger.addHandler(fh)
    logger.addHandler(ch)

    return logger


_log = __log()


def log(*args, **kwargs):

    logger = {
        'debug': _log.debug,
        'info': _log.info,
        'error': _log.error,
    }

    if 'level' in kwargs:
        lg = logger[kwargs['level']]
    else:
        lg = logger['info']

    message = ''.join([str(i) for i in args])

    lg(message)


#
# 实现函数
def path_with_query(path, query):
    '''
    path 是一个字符串
    query 是一个字典

    返回一个拼接后的 url
    详情请看下方测试函数
    '''
    s = []
    for k, v in query.items():
        s.append('='.join([str(k), str(v)]))
    parsed = '&'.join(s)
    result = path + '?' + parsed
    log('function path_with_query: ', result, level='debug')
    return result


# 1
# 补全函数
def protocol_of_url(url):
    '''
    url 是字符串, 可能的值如下
    'g.cn'
    'g.cn/'
    'g.cn:3000'
    'g.cn:3000/search'
    'http://g.cn'
    'https://g.cn'
    'http://g.cn/'

    返回代表协议的字符串, 'http' 或者 'https'
    '''
    url = url.strip()
    protocol = 'http'
    if '://' in url:
        protocol = url.split('://')[0]
    else:
        pass
    return protocol



def host_port_of_url(url):
    '''
    返回host和port
    '''
    url = url.strip()
    if '://' in url:
        tmp = url.split('://')[1]
        if '/' in tmp:
            host_port = tmp.split('/')[0]
        else:
            host_port = tmp
    elif '/' in url:
        host_port = url.split('/')[0]
    else:
        host_port = url
    return host_port
# 2
# 补全函数
def host_of_url(url):
    '''
    url 是字符串, 可能的值如下
    'g.cn'
    'g.cn/'
    'g.cn:3000'
    'g.cn:3000/search'
    'http://g.cn'
    'https://g.cn'
    'http://g.cn/'

    返回代表主机的字符串, 比如 'g.cn'
    '''
    url = url.strip()
    host_port = host_port_of_url(url)
    if ':' in host_port:
        host = host_port.split(':')[0]
    else:
        host = host_port
    return host


# 3
# 补全函数
def port_of_url(url):
    '''
    url 是字符串, 可能的值如下
    'g.cn'
    'g.cn/'
    'g.cn:3000'
    'g.cn:3000/search'
    'http://g.cn'
    'https://g.cn'
    'http://g.cn/'

    返回代表端口的字符串, 比如 '80' 或者 '3000'
    注意, 如上课资料所述, 80 是默认端口
    '''
    url = url.strip()
    host_port = host_port_of_url(url)
    protocol = protocol_of_url(url)
    if ':' in host_port:
        port = host_port.split(':')[1]
    elif protocol == 'https':
        port = 443
    elif protocol == 'http':
        port = 80
    else:
        port = 80
    return int(port)


# 4
# 补全函数
def path_of_url(url):
    '''
    url 是字符串, 可能的值如下
    'g.cn'
    'g.cn/'
    'g.cn:3000'
    'g.cn:3000/search'
    'http://g.cn'
    'https://g.cn'
    'http://g.cn/'

    返回代表路径的字符串, 比如 '/' 或者 '/search'
    注意, 如上课资料所述, 当没有给出路径的时候, 默认路径是 '/'
    '''
    url = url.strip()
    host = host_port_of_url(url)
    url_split_host = url.split(host)

    if  url_split_host[-1] == '':
        path = '/'
    else:
        path = url_split_host[-1]

    return path


# 4
# 补全函数
def parsed_url(url):
    '''
    url 是字符串, 可能的值如下
    'g.cn'
    'g.cn/'
    'g.cn:3000'
    'g.cn:3000/search'
    'http://g.cn'
    'https://g.cn'
    'http://g.cn/'
    返回一个 tuple, 内容如下 (protocol, host, port, path)
    '''

    protocol = protocol_of_url(url)
    host = host_of_url(url)
    port = port_of_url(url)
    path = path_of_url(url)
    return (protocol, host, port, path)


def socket_by_protocol(protocol):
    """根据protocol的类型返回socket"""
    if protocol == 'https':
        s = ssl.wrap_socket(socket.socket(socket.AF_INET, socket.SOCK_STREAM))
    else:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    return s


def response_by_socket(s, encode='utf-8'):
    """参数时一个socket 实例
    return socket的所有返回
    """
    response = b''
    while True:
        tmp = s.recv(1024)
        response += tmp
        if len(tmp) < 1024:
            break

    return response.decode(encode)


def send_request(s, r, encode='utf-8'):
    """s,socket r,request string
    return: socket
    """

    request = r.encode(encode)
    s.send(request)

    return s


def parsed_response(r):
    """把response 解析出 状态码 headers body 返回
    状态码时int
    """
    begin, body = r.split('\r\n\r\n', 1)
    h = begin.split('\r\n')
    status_code = h[0].split(' ')[1]
    headers = {}
    for i in h[1:]:
        k, v = i.split(': ')
        headers[k.lower()] = v
    return status_code, headers, body


# 实现函数
def header_from_dict(headers):
    '''
    headers 是一个字典
    范例如下
    对于
    {
    	'Content-Type': 'text/html',
        'Content-Length': 127,
    }
    返回如下 str
    'Content-Type: text/html\r\nContent-Length: 127\r\n'
    '''
    a = []
    for k, v in headers.items():
        a.append(': '.join([str(k),str(v)]))
    return '\r\n'.join(a)


# 5
# 把向服务器发送 HTTP 请求并且获得数据这个过程封装成函数
# 定义如下
def get(url, query={}):
    '''
    本函数使用上课代码 client.py 中的方式使用 socket 连接服务器
    获取服务器返回的数据并返回
    注意, 返回的数据类型为 bytes
    '''
    (protocol, host, port, path) = parsed_url(url)
    if query:
        path = path_with_query(path, query)

    header = {
        "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Accept-Language": "zh-CN,en-US;q=0.7,en;q=0.3",
        "Accept-Encoding": "gzip, deflate, br",
        "Connection": "keep-alive",
        "Upgrade-Insecure-Requests": "1",
        "DNT": "1",
    }

    headers = header_from_dict(header)

    s = socket_by_protocol(protocol)
    s.connect((host, port))

    http_request = 'GET {} HTTP/1.1\r\nhost:{}\r\n\r\n'.format(path, host)
    s = send_request(s, http_request)
    ip, port = s.getsockname()

    response = response_by_socket(s)
    status_code, headers, body = parsed_response(response)
    log('function get', status_code, headers, level='debug')

    if status_code in ['301','302']:
        status_code, headers, body = get(headers['location'])
    return status_code, headers, body




# 使用
def main():

    url = 'http://movie.douban.com/top250'
    r = get(url,)
#   log(url, r, level='debug')
    return




if __name__ == '__main__':
    main()
